﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;

namespace FBGraph.ClientGenerator.CodeGen
{
    public class CodeGenType : Member
    {
        private CodeGenType()
        {
            this.Imports = new ImportsList("System");

            this.TypeArguments = new TypeArgumentsList();
            this.InterfaceImplementations = new ImplementationList();

            this.Constructors = new ConstructorList();
            this.Properties = new PropertyList(this);
            this.Methods = new MethodList();

            this.NestedTypes = new TypeList();
            this.SubClasses = new List<CodeGenType>();
        }

        private CodeGenType(String name) : this(name, null)
        {
            this.IsNested = true;            
        }

        public CodeGenType(String name, String namespaceName)
            : this()
        {
            this.Name = name;
            this.Namespace = namespaceName;
        }

        public String Name { get; set; }
        public TypeArgumentsList TypeArguments { get; private set; }
        public TypeReference BaseType { get; private set; }
        public ImplementationList InterfaceImplementations { get; private set; }
        public String Namespace { get; set; }
        public ConstructorList Constructors { get; set; }
        public PropertyList Properties { get; private set; }
        public MethodList Methods { get; private set; }
        public TypeList NestedTypes { get; private set; }
        public List<CodeGenType> SubClasses { get; private set; }

        private Boolean IsNested { get; set; }

        public ImportsList Imports { get; private set; }

        public CodeGenType GetNestedType(String name)
        {
            var nestedType = this.NestedTypes.SingleOrDefault(t => t.Name == name);
            if (nestedType == null)
            {
                nestedType = new CodeGenType(name);
                this.NestedTypes.Add(nestedType);
                return nestedType;
            }
            else return nestedType;
        }

        public void InheritFrom(CodeGenType type)
        {
            this.InheritFrom(new TypeReference(type));
        }

        public void InheritFrom(TypeReference type)
        {
            this.BaseType = type;
            if (type.IsGeneratedType) type.GeneratedType.SubClasses.Add(this);
        }

        public TypeReference ToTypeReference()
        {
            return new TypeReference(this);
        }

        protected override void BeforeWrite()
        {
            if (this.InterfaceImplementations.Any(i => i.Type == typeof(IFBGraphResult)))
            {
                var ifaceref = new TypeReference(typeof(IFBGraphResult));
                var isReady = new AutoProperty("IsReady", typeof(Boolean))
                {
                    ExplicitImplementationType = ifaceref
                };
                isReady.XmlDocComments.Suggest("<summary>Gets or sets a <see cref=\"Boolean\" /> value representing whether the object has been populated.</summary>");

                var updateFrom = new Method("UpdateFrom", null, new Parameter("other", ifaceref))
                {
                    ExplicitImplementationType = ifaceref                    
                };
                updateFrom.XmlDocComments.Suggest("<summary>Updates the current model with values from another like-typed model.</summary>");
                updateFrom.XmlDocComments.Suggest("<param name=\"other\">The source model.</param>");
                updateFrom.XmlDocComments.Suggest("<remarks>This is used for asynchronous calls to update the reference that was returned when the original API method was called.</remarks>");

                using (var body = updateFrom.GetBodyWriter())
                {
                    body.WriteLine("var that = other as {0};", this.Name);
                    body.WriteLine("if (that == null) return;");
                    body.WriteLine("else");
                    body.WriteLine("{");
                    using (body.Indent())
                    {
                        var last = this.Properties.LastOrDefault();
                        foreach (var prop in this.Properties.Where(p => p.ExplicitImplementationType == null))
                        {
                            body.WriteLine("this.{0} = that.{0};", prop.Name);
                        }
                        body.WriteLine("((IFBGraphResult)this).IsReady = true;");
                    }
                    body.WriteLine("}");
                }

                this.Properties.Suggest(isReady);
                this.Methods.Suggest(updateFrom);
            }
        }

        protected virtual void WriteImports(CodeGenWriter writer)
        {
            if (!this.IsNested)
            {
                writer.Write(this.Imports);
                writer.WriteLine();
            }
        }

        protected virtual void WriteStartNamespace(CodeGenWriter writer)
        {
            if (!String.IsNullOrEmpty(this.Namespace))
            {
                writer.WriteLine("namespace {0}", this.Namespace);
                writer.WriteLine('{');
            }
        }

        protected override void WriteDeclaration(CodeGenWriter writer)
        {
            writer.Write("public partial class {0}", this.Name);
            writer.Write(this.TypeArguments);

            if (this.BaseType != null)
            {
                writer.Write(" : ");
                writer.Write(this.BaseType);
                if (this.InterfaceImplementations.Any())
                {
                    writer.Write(", ");
                }
            }
            else if (this.InterfaceImplementations.Any()) writer.Write(" : ");

            writer.Write(this.InterfaceImplementations);

            writer.WriteLine();
        }

        protected virtual void WriteBodyStart(CodeGenWriter writer)
        {
            writer.WriteLine('{');
        }

        protected virtual void WriteBodyEnd(CodeGenWriter writer)
        {
            writer.WriteLine("}");
        }

        protected virtual void WriteEndNamespace(CodeGenWriter writer)
        {
            if (!String.IsNullOrEmpty(this.Namespace))
            {
                writer.Write('}');
            }
        }

        protected override void WriteBody(CodeGenWriter writer)
        {
            writer.Write(this.Constructors);

            if (this.Properties.Any() && this.Constructors.Any()) writer.WriteLine();
            writer.Write(this.Properties);

            if (this.NestedTypes.Any() && (this.Properties.Any() || this.Methods.Any())) writer.WriteLine();
            writer.Write(this.Methods);

            if (this.NestedTypes.Any() && (this.Properties.Any() || this.Methods.Any() || this.Constructors.Any())) writer.WriteLine();
            writer.Write(this.NestedTypes);
        }

        public override void Write(CodeGenWriter writer)
        {
            WriteImports(writer);
            if (this.IsNested) base.Write(writer);
            else
            {
                WriteStartNamespace(writer);
                using (writer.Indent())
                {
                    base.Write(writer);
                }
                WriteEndNamespace(writer);
            }
        }
    }
}